package com.hanter.rad.widget.cupertino;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import com.hanter.rad.widget.R;

import java.util.ArrayList;
import java.util.List;

public class PickerView extends View implements Picker {

    private static final String TAG = "PickerView";
    private static final boolean DEBUG = false;

    private static final float DEFAULT_LINE_SPACE = 1.0f;
    private static final int DEFAULT_VISIBLE_ITEM_COUNT = 9;
    private static final int TEXT_PADDING_VERTICAL = 8; // dp单位
    private static final int INVALID_POSITION = -1;

    /**
     * 停止滚动
     */
    public static final int SCROLL_STATE_IDLE = 0;
    /**
     * 用户按住滚轮拖拽
     */
    public static final int SCROLL_STATE_DRAGGING = 1;
    /**
     * 依靠惯性滚动
     */
    public static final int SCROLL_STATE_FLING = 2;
    /**
     * 用户设置
     */
    public static final int SCROLL_STATE_SETTING = 3;

    public enum Action {
        CLICK, FLING, DRAG
    }

    private GestureDetector flingGestureDetector;
    @Nullable
    private OnItemChangeListener onItemChangeListener;

    private int componentDragged = -1;

    private Paint paintOuterText;
    private Paint paintCenterText;
    private Paint paintIndicator;

    private int textSize;
    private int textHeight; // 文本的高度
    private int itemTextHeight;

    private int outerTextColor;
    private int centerTextColor;
    private int dividerColor;

    private int itemHeight;
    private int halfItemHeight;
    private int componentWidth; // 默认的componentWidth
    private int separatorLineTop;
    private int separatorLineBottom;
    private int radius;
    private int halfCircumference;

    private List<ComponentInfo> componentInfos;

    float scaleX = 1.05F;

    private float lineSpacingMultiplier;
    private int itemsVisibleCount;

    private long lastDownTime; // ACTION_DOWN最后一次的时间

    private Rect rect = new Rect();

    @Nullable
    private PickerViewDataSource dataSource;
    @Nullable
    private PickerViewDelegate pickerDelegate;

    private boolean showSelectionIndicator = true;
    private boolean loop;

    public PickerView(Context context) {
        this(context, null);
    }

    public PickerView(Context context, AttributeSet attributeset) {
        this(context, attributeset, 0);
    }

    public PickerView(Context context, AttributeSet attributeset, int defStyleAttr) {
        super(context, attributeset, defStyleAttr);
        init(context, attributeset, defStyleAttr);
    }

    private void init(Context context, AttributeSet attributeset, int defStyleAttr) {
        componentInfos = new ArrayList<>();
        flingGestureDetector = new GestureDetector(context, new PickerViewGestureListener());
        flingGestureDetector.setIsLongpressEnabled(false);

        TypedArray typedArray = context.obtainStyledAttributes(attributeset, R.styleable.PickerView);
        if (typedArray != null) {
            int defaultTextSize = (int) (getResources().getDisplayMetrics().density * 15);
            textSize = typedArray.getDimensionPixelSize(R.styleable.PickerView_pkv_textSize, defaultTextSize);
            centerTextColor = typedArray.getInteger(R.styleable.PickerView_pkv_centerTextColor, 0xFF333333);
            outerTextColor = typedArray.getInteger(R.styleable.PickerView_pkv_outerTextColor, 0XFFDEDEDE);
            dividerColor = typedArray.getInteger(R.styleable.PickerView_pkv_dividerTextColor, 0XFFC5C5C5);
            lineSpacingMultiplier = typedArray.getFloat(R.styleable.PickerView_pkv_lineSpace, DEFAULT_LINE_SPACE);
            itemsVisibleCount = typedArray.getInteger(R.styleable.PickerView_pkv_itemsVisibleCount, DEFAULT_VISIBLE_ITEM_COUNT);
            if (itemsVisibleCount % 2 == 0) {
                itemsVisibleCount = DEFAULT_VISIBLE_ITEM_COUNT;
            }
            loop = typedArray.getBoolean(R.styleable.PickerView_pkv_loop, false);
            typedArray.recycle();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (dataSource == null || dataSource.getNumberOfComponentsInPickerView() <= 0
                || pickerDelegate == null) {
            return;
        }

        for (int component = 0; component < componentInfos.size(); component++) {
            ComponentInfo componentInfo = componentInfos.get(component);

            if (componentInfo == null) {
                continue;
            }

            // 计算当前组件位置
            updateComponentPosition(componentInfo);

            buildDrawingStrings(componentInfo);

            drawComponentStrings(canvas, componentInfo);
        }

        if (showSelectionIndicator) {
            canvas.drawLine(getPaddingLeft(),
                    separatorLineTop,
                    getMeasuredWidth() - getPaddingRight(),
                    separatorLineTop,
                    paintIndicator);
            canvas.drawLine(getPaddingLeft(),
                    separatorLineBottom,
                    getMeasuredWidth() - getPaddingRight(),
                    separatorLineBottom,
                    paintIndicator);
        }
    }

    private void drawComponentStrings(@NonNull Canvas canvas, @NonNull ComponentInfo componentInfo) {
        int component = componentInfo.index;
        int extraOffset = componentInfo.totalScrollY % itemHeight;

        // draw items
        int i = 0;
        while (i < itemsVisibleCount) {
            canvas.save();

            double radian = ((itemHeight * i - extraOffset) * Math.PI) / halfCircumference;

            // item未超过当前半圆，则会绘制
            if (radian < Math.PI && radian > 0) {
                int translateY = (int) (radius - Math.cos(radian) * radius - (Math.sin(radian) * itemTextHeight) / 2);

                canvas.translate(0.0F, translateY);
                canvas.scale(1.0F, (float) Math.sin(radian));

                int start = componentInfo.startX;
                int end;
                if (componentInfo.width == 0) {
                    end = componentInfo.startX + componentWidth;
                } else {
                    end = componentInfo.startX + componentInfo.width;
                }

                if (translateY <= separatorLineTop && itemTextHeight + translateY >= separatorLineTop) {
                    // first divider
                    canvas.save();
                    canvas.clipRect(start, 0, end, separatorLineTop - translateY);
                    drawOuterText(canvas, component, i);
                    canvas.restore();
                    canvas.save();
                    canvas.clipRect(start, separatorLineTop - translateY, end, itemHeight);
                    drawCenterText(canvas, component, i);
                    canvas.restore();
                } else if (translateY <= separatorLineBottom && itemTextHeight + translateY >= separatorLineBottom) {
                    // second divider
                    canvas.save();
                    canvas.clipRect(start, 0, end, separatorLineBottom - translateY);
                    drawCenterText(canvas, component, i);
                    canvas.restore();
                    canvas.save();
                    canvas.clipRect(start, separatorLineBottom - translateY, end, itemHeight);
                    drawOuterText(canvas, component, i);
                    canvas.restore();
                } else if (translateY >= separatorLineTop && itemTextHeight + translateY <= separatorLineBottom) {
                    // center item
                    canvas.clipRect(start, 0, end, itemHeight);
                    drawCenterText(canvas, component, i);
                } else {
                    // other item
                    canvas.clipRect(start, 0, end, itemHeight);
                    drawOuterText(canvas, component, i);
                }
            }
            canvas.restore();
            i++;
        }
    }

    private void buildDrawingStrings(@NonNull ComponentInfo componentInfo) {
        if (dataSource == null || pickerDelegate == null)
            return;

        int component = componentInfo.index;
        // put value to drawingString
        int i = 0;
        while (i < itemsVisibleCount) {
            // Item Index 相对于中间位置
            int itemIndex = componentInfo.currentItem - (itemsVisibleCount / 2 - i);
            int rowCount = dataSource.getNumberOfRowsInComponent(component);

            if (loop) {
                while (itemIndex < 0) {
                    itemIndex = itemIndex + rowCount;
                }
                while (itemIndex > rowCount - 1) {
                    itemIndex = itemIndex - rowCount;
                }
                componentInfo.drawingStrings.put(i, pickerDelegate.getTitleForRow(itemIndex, component));
            } else if (itemIndex < 0) {
                componentInfo.drawingStrings.put(i, "");
            } else if (itemIndex > rowCount - 1) {
                componentInfo.drawingStrings.put(i, "");
            } else {
                componentInfo.drawingStrings.put(i, pickerDelegate.getTitleForRow(itemIndex, component));
            }

            i++;
        }
    }

    private void updateComponentPosition(@NonNull ComponentInfo componentInfo) {
        int currentItem = calculateComponentPosition(componentInfo);
        if (componentInfo.currentItem != currentItem) {
            componentInfo.currentItem = currentItem;
        }
    }

    private int calculateComponentPosition(@NonNull ComponentInfo componentInfo) {
        if (dataSource == null || dataSource.getNumberOfRowsInComponent(componentInfo.index) == 0)
            return INVALID_POSITION;

        int rowCount = dataSource.getNumberOfRowsInComponent(componentInfo.index);
        int extraOffset = componentInfo.totalScrollY % itemHeight;
        int currentItem = componentInfo.initPosition + componentInfo.totalScrollY / itemHeight;

//        if (Math.abs(extraOffset) > halfItemHeight) {
//            currentItem += (extraOffset > 0) ? 1 : -1;
//        }

        currentItem %= rowCount;

        if (currentItem < 0) {
            currentItem = loop ? (currentItem + rowCount) : 0;
        }

        return currentItem;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();

        if (isDataEmpty()) {
            return;
        }


        for (int component = 0; component < componentInfos.size(); component++) {
            ComponentInfo componentInfo = componentInfos.get(component);
            if (componentInfo.scroller.computeScrollOffset()) {
                componentInfo.totalScrollY = componentInfo.scroller.getCurrY();

                if (componentInfo.scroller.isFinished()) {
                    if (componentInfo.currentScrollState == SCROLL_STATE_FLING) {

                        if (isRightScrollY(componentInfo.totalScrollY)) {
                            onChangeScrollState(componentInfo, SCROLL_STATE_IDLE);
                            return;
                        }

                        int destItemOffset = calculateDestItemOffset(componentInfo.totalScrollY % itemHeight);

                        if ((System.currentTimeMillis() - lastDownTime) > 120) {
                            smoothScroll(componentInfo, Action.FLING);
                        } else if (canScroll(component, destItemOffset)) {
                            smoothScroll(componentInfo, Action.CLICK, destItemOffset);
                        }

                    } else if (componentInfo.currentScrollState == SCROLL_STATE_DRAGGING
                            || componentInfo.currentScrollState == SCROLL_STATE_SETTING
                            && componentDragged != component) {
                        // 滑动或设置 结束，则设定位置
                        onChangeScrollState(componentInfo, SCROLL_STATE_IDLE);
                    }

                }

                // 处理滚动位置监听回调
                if (componentInfo.currentScrollState == SCROLL_STATE_DRAGGING
                        || componentInfo.currentScrollState == SCROLL_STATE_FLING) {

                    updateComponentPosition(componentInfo);

                    onItemScrolling(componentInfo.currentItem,
                            componentInfo.index,
                            componentInfo.currentScrollState);
                }
            }

        }

        ViewCompat.postInvalidateOnAnimation(PickerView.this);
    }

    @Override
    public int getNumberOfComponents() {
        return dataSource != null ? dataSource.getNumberOfComponentsInPickerView() : 0;
    }

    @Override
    public int getNumberOfRows(int component) {
        return dataSource != null ? dataSource.getNumberOfRowsInComponent(component) : 0;
    }

    @Override
    public Size getRowSize(int component) {
        return null;
    }

    @Override
    public int getSelectedIndex(int component) {
        ComponentInfo com = getComponentInfo(component);
        return com != null ? com.currentItem : INVALID_POSITION;
    }

    @Override
    public void selectRow(int row, int component) {
        selectRow(row, component, true);
    }

    @Override
    public void selectRow(int row, int component, boolean animate) {
        ComponentInfo componentInfo = getComponentInfo(component);

        if (componentInfo == null) {
            return;
        }

        componentInfo.scroller.forceFinished(true);
        int startY = componentInfo.scroller.getCurrY();
        int dstY = calculateItemScrollY(row, componentInfo.initPosition, itemHeight);
        int dy = dstY - startY;
        if (animate) {
            componentInfo.scroller.startScroll(0, startY, 0, dy);
        } else {
            componentInfo.scroller.startScroll(0, startY, 0, dy, 0);
        }
    }

    private boolean isDataEmpty() {
        return dataSource == null || dataSource.getNumberOfComponentsInPickerView() == 0;
    }

    private boolean isRightScrollY(int scrollY) {
        return (itemHeight == 0 || scrollY % itemHeight == 0);
    }

    /**
     * visible item count, must be odd number
     *
     * @param visibleCount 可见的数目
     */
    public void setItemsVisibleCount(int visibleCount) {
        if (visibleCount % 2 == 0) {
            return;
        }

        if (visibleCount != itemsVisibleCount) {
            itemsVisibleCount = visibleCount;
            measure(getMeasuredWidthAndState(), getMeasuredHeightAndState());
            ViewCompat.postInvalidateOnAnimation(PickerView.this);
        }
    }

    private void initPaintsIfPossible() {
        if (paintOuterText == null) {
            paintOuterText = new Paint();
            paintOuterText.setColor(outerTextColor);
            paintOuterText.setAntiAlias(true);
            paintOuterText.setTextSize(textSize);
        }

        if (paintCenterText == null) {
            paintCenterText = new Paint();
            paintCenterText.setColor(centerTextColor);
            paintCenterText.setAntiAlias(true);
            paintCenterText.setTextScaleX(scaleX);
            paintCenterText.setTextSize(textSize);
        }

        if (paintIndicator == null) {
            paintIndicator = new Paint();
            paintIndicator.setColor(dividerColor);
            paintIndicator.setAntiAlias(true);
        }
    }

    /**
     * set text line space, must more than 1
     *
     * @param lineSpacingMultiplier lineSpacingMultiplier
     */
    public void setLineSpacingMultiplier(float lineSpacingMultiplier) {
        if (lineSpacingMultiplier > 1.0f) {
            this.lineSpacingMultiplier = lineSpacingMultiplier;
        }
    }

    /**
     * set outer text color
     *
     * @param centerTextColor centerTextColor
     */
    public void setCenterTextColor(int centerTextColor) {
        this.centerTextColor = centerTextColor;
        if (paintCenterText != null) {
            paintCenterText.setColor(centerTextColor);
        }

    }

    /**
     * set center text color
     *
     * @param outerTextColor outerTextColor
     */
    public void setOuterTextColor(int outerTextColor) {
        this.outerTextColor = outerTextColor;
        if (paintOuterText != null) {
            paintOuterText.setColor(outerTextColor);
        }
    }

    /**
     * set divider color
     *
     * @param dividerColor dividerColor
     */
    public void setDividerColor(int dividerColor) {
        this.dividerColor = dividerColor;
        if (paintIndicator != null) {
            paintIndicator.setColor(dividerColor);
        }
    }

    /**
     * set text typeface
     *
     * @param typeface typeface
     */
    public void setTypeface(Typeface typeface) {
        this.paintCenterText.setTypeface(typeface);
    }

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void reloadComponent(int component) {
        calculateComponentScrollY(component);

        int count = 0;
        if (dataSource != null) {
            count = dataSource.getNumberOfRowsInComponent(component);
        }

        ComponentInfo componentInfo = getComponentInfo(component);
        if (componentInfo != null) {
            componentInfo.scroller.forceFinished(true);

            int lastItem = componentInfo.currentItem;
            if (lastItem > (count - 1)) {
                componentInfo.currentItem = Math.max(0, count - 1);
            }

            Log.d(TAG, "reloadComponent, count: " + count + ", preItem: " + lastItem + ", curItem: " + componentInfo.currentItem);

            componentInfo.totalScrollY = calculateItemScrollY(componentInfo.currentItem, componentInfo.initPosition, itemHeight);
            componentInfo.drawingStrings.clear();
        }

        postInvalidate();
    }

    private int calculateItemScrollY(int currentItem, int initPosition, int itemHeight) {
        return (currentItem - initPosition) * itemHeight;
    }

    @Override
    public void reloadAllComponents() {
        calculateComponentsMeasure();
        postInvalidate();
    }

    private void calculateComponentsMeasure() {
        int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        int totalWeight = 0;

        // calculate component scrollY
        for (int i = 0; i < componentInfos.size(); i++) {
            if (pickerDelegate == null)
                continue;

            ComponentInfo componentInfo = componentInfos.get(i);

            int componentWidth = pickerDelegate.getWidthForComponent(i);

            if (componentWidth == 0) {
                totalWeight++;
            } else {
                availableWidth -= componentWidth;
                componentInfo.width = componentWidth;
            }

            // init position
            int initPosition = pickerDelegate.getInitPositionInComponent(i);

            if (initPosition == -1 && dataSource != null && loop) {
                componentInfo.initPosition = (dataSource.getNumberOfRowsInComponent(i) + 1) / 2;
            } else {
                componentInfo.initPosition = initPosition;
            }

            componentInfo.currentItem = componentInfo.initPosition;

            calculateComponentScrollY(i);
        }

        if (totalWeight == 0) {
            componentWidth = 0;
        } else if (availableWidth > 0) {
            componentWidth = availableWidth / totalWeight;
        } else {
            componentWidth = 0;
        }

        int startX = getPaddingLeft();

        for (int i = 0; i < componentInfos.size(); i++) {
            ComponentInfo componentInfo = componentInfos.get(i);
            componentInfo.startX = startX;

            if (componentInfo.width == 0) {
                startX += componentWidth;
            } else {
                startX += componentInfo.width;
            }
        }
    }

    private void calculateComponentScrollY(int component) {
        ComponentInfo componentInfo = getComponentInfo(component);

        if (dataSource == null || componentInfo == null)
            return;

        if (loop) {
            componentInfo.minScrollY = Integer.MAX_VALUE / itemHeight * itemHeight;
            componentInfo.maxScrollY = Integer.MAX_VALUE / itemHeight * itemHeight;
        } else {
            componentInfo.minScrollY = (-componentInfo.initPosition) * itemHeight;
            componentInfo.maxScrollY = (dataSource.getNumberOfRowsInComponent(component) - 1 - componentInfo.initPosition) * itemHeight;
        }
    }

    void smoothScroll(int component, Action action) {
        smoothScroll(component, action, 0);
    }

    void smoothScroll(ComponentInfo componentInfo, Action action) {
        smoothScroll(componentInfo, action, 0);
    }

    void smoothScroll(int component, Action action, int destItemOffset) {
        ComponentInfo componentInfo = getComponentInfo(component);
        if (componentInfo == null) {
            return;
        }
        smoothScroll(componentInfo, action, destItemOffset);
    }

    void smoothScroll(ComponentInfo componentInfo, Action action, int destItemOffset) {
        componentInfo.scroller.forceFinished(true);

        if (action == Action.FLING) {
            onChangeScrollState(componentInfo, SCROLL_STATE_FLING);
            destItemOffset = calculateDestItemOffset(componentInfo.totalScrollY % itemHeight);
            componentInfo.scroller.startScroll(0, componentInfo.scroller.getCurrY(), 0, destItemOffset);
        } else if (action == Action.DRAG) {
            onChangeScrollState(componentInfo, SCROLL_STATE_DRAGGING);
            destItemOffset = calculateDestItemOffset(componentInfo.totalScrollY % itemHeight);
            componentInfo.scroller.startScroll(0, componentInfo.scroller.getCurrY(), 0, destItemOffset);
        } else {
            onChangeScrollState(componentInfo, SCROLL_STATE_SETTING);
            componentInfo.scroller.startScroll(0, componentInfo.scroller.getCurrY(), 0, destItemOffset);
        }

        invalidate();
    }

    private void onChangeScrollState(int component, int scrollState) {
        ComponentInfo componentInfo = getComponentInfo(component);

        if (componentInfo != null) {
            onChangeScrollState(componentInfo, scrollState);
        }
    }

    private void onChangeScrollState(ComponentInfo componentInfo, int scrollState) {
        if (scrollState != componentInfo.currentScrollState) {
            int lastScrollState = componentInfo.currentScrollState;
            componentInfo.currentScrollState = scrollState;
            if (onItemChangeListener != null) {
                onItemChangeListener.onItemScrollStateChanged(PickerView.this, componentInfo.currentItem,
                        lastScrollState, componentInfo.currentScrollState, componentInfo.totalScrollY);
            }

            if (scrollState == SCROLL_STATE_IDLE) {
                // Log.d(TAG, "component: currentItem-" + componentInfo.currentItem + ", scrollY-" + componentInfo.totalScrollY);
                onItemSelected(componentInfo);
            }

            postInvalidate();
        }
    }

    @Nullable
    private ComponentInfo getComponentInfo(int component) {
        try {
            return componentInfos.get(component);
        } catch (IndexOutOfBoundsException e) {
            return null;
        }
    }

    /**
     * set not loop
     */
    public void setNotLoop() {
        loop = false;
    }

    /**
     * set text size in px
     *
     * @param size size
     */
    public final void setTextSize(float size) {
        if (size > 0.0F) {
            textSize = (int) size;
            if (paintOuterText != null) {
                paintOuterText.setTextSize(textSize);
            }
            if (paintCenterText != null) {
                paintCenterText.setTextSize(textSize);
            }

        }
    }

    public final void setOnItemChangeListener(@Nullable OnItemChangeListener onItemChangeListener) {
        this.onItemChangeListener = onItemChangeListener;
    }

    public void setDataSource(PickerViewDataSource dataSource) {
        this.dataSource = dataSource;

        componentInfos.clear();

        if (dataSource != null) {
            for (int i = 0; i < dataSource.getNumberOfComponentsInPickerView(); i++) {
                ComponentInfo componentInfo = new ComponentInfo(i);
                componentInfo.scroller = new Scroller(getContext());
                componentInfos.add(componentInfo);
            }
        }

        calculateComponentsMeasure();
        postInvalidate();
    }

    @Nullable
    public PickerViewDataSource getDataSource() {
        return dataSource;
    }

    public void setPickerDelegate(PickerViewDelegate pickerDelegate) {
        this.pickerDelegate = pickerDelegate;
        calculateComponentsMeasure();
        postInvalidate();
    }

    @Nullable
    public PickerViewDelegate getPickerDelegate() {
        return pickerDelegate;
    }

    protected void onItemSelected(ComponentInfo componentInfo) {
        if (DEBUG)
            Log.d(TAG, "onItemSelected");

        if (componentInfo.lastItem == componentInfo.currentItem) {
            return;
        }

        componentInfo.lastItem = componentInfo.currentItem;

        if (onItemChangeListener != null) {
            onItemChangeListener.onItemSelected(componentInfo.currentItem, componentInfo.index);
        }

        if (pickerDelegate != null) {
            pickerDelegate.didSelectRow(componentInfo.currentItem, componentInfo.index);
        }
    }

    protected void onItemScrolling(int position, int component, int currentScrollState) {
        ComponentInfo componentInfo = getComponentInfo(component);

        if (componentInfo == null || onItemChangeListener == null)
            return;

        if (componentInfo.scrollItem != position) {
            componentInfo.scrollItem = position;
            onItemChangeListener.onItemScrolling(PickerView.this,
                    position,
                    componentInfo.index,
                    componentInfo.currentScrollState);
        }
    }

    /**
     * link https://github.com/weidongjian/androidWheelView/issues/10
     *
     * @param scaleX scaleX
     */
    public void setScaleX(float scaleX) {
        this.scaleX = scaleX;
    }

    private void drawOuterText(Canvas canvas, int component, int position) {
        ComponentInfo componentInfo = getComponentInfo(component);
        if (componentInfo == null) {
            return;
        }

        canvas.drawText(componentInfo.drawingStrings.get(position),
                getTextX(component, componentInfo.drawingStrings.get(position), paintOuterText, rect),
                getDrawingY(),
                paintOuterText);
    }

    private void drawCenterText(Canvas canvas, int component, int position) {
        ComponentInfo componentInfo = getComponentInfo(component);
        if (componentInfo == null) {
            return;
        }

        canvas.drawText(componentInfo.drawingStrings.get(position),
                getTextX(component, componentInfo.drawingStrings.get(position), paintCenterText, rect),
                getDrawingY(),
                paintCenterText);
    }

    private int getDrawingY() {
        if (itemTextHeight > textHeight) {
            return itemTextHeight - ((itemTextHeight - textHeight) / 2);
        } else {
            return itemTextHeight;
        }
    }

    // text start drawing position
    private int getTextX(int component, String text, Paint paint, Rect rect) {
        paint.getTextBounds(text, 0, text.length(), rect);
        int textWidth = (int) (rect.width() * scaleX);

        ComponentInfo componentInfo = componentInfos.get(component);

        if (componentInfo.width == 0) {
            return componentInfo.startX + (componentWidth - textWidth) / 2;
        } else {
            return componentInfo.startX + (componentInfo.width - textWidth) / 2;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        initPaintsIfPossible();

        paintCenterText.getTextBounds("\u661F\u671F", 0, 2, rect); // 星期
        textHeight = rect.height();

        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (measuredWidth == 0 && measuredHeight == 0) {
            return;
        }

        final int specMode = MeasureSpec.getMode(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) { // 固定高度时
            halfCircumference = (int) (measuredHeight * Math.PI / 2);
            itemTextHeight = (int) (halfCircumference / (lineSpacingMultiplier * (itemsVisibleCount - 1)));
        } else { // 自适应高度时
            int padding = ((int) (2 * TEXT_PADDING_VERTICAL * getResources().getDisplayMetrics().density + 0.5f));
            itemTextHeight = padding + textHeight;
            halfCircumference = (int) (lineSpacingMultiplier * (itemsVisibleCount - 1) * itemTextHeight);
            int minMeasureHeight = (int) ((2 * halfCircumference / Math.PI) + 0.5f);
            measuredHeight = resolveSize(minMeasureHeight, heightMeasureSpec);
        }

        radius = measuredHeight / 2;

        itemHeight = (int) ((lineSpacingMultiplier * itemTextHeight) + 0.5f);
        halfItemHeight = (int) ((itemHeight / 2.0f) + 0.5f);

        separatorLineTop = (int) ((measuredHeight - itemHeight) / 2.0F);
        separatorLineBottom = (int) ((measuredHeight + itemHeight) / 2.0F);

        calculateComponentsMeasure();

        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean eventConsumed = flingGestureDetector.onTouchEvent(event);

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {

                lastDownTime = System.currentTimeMillis();

                int componentCount = componentInfos.size();
                componentDragged = getComponentInRect(event.getX(), event.getY());
                ComponentInfo componentInfo = getComponentInfo(componentDragged);
                if (componentInfo != null) {
                    componentInfo.scroller.forceFinished(true);
                    onChangeScrollState(componentInfo, SCROLL_STATE_DRAGGING);
                }

                ViewCompat.postInvalidateOnAnimation(PickerView.this);

                if (getParent() != null) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
            }
            break;

            case MotionEvent.ACTION_MOVE:

                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
            default:

                int component = componentDragged;
                componentDragged = -1;

                if (getParent() != null) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }

                ComponentInfo componentInfo = getComponentInfo(component);

                if (dataSource == null || dataSource.getNumberOfRowsInComponent(component) <= 0) {
                    return true;
                }

                if (componentInfo != null) {
                    if (componentInfo.currentScrollState == SCROLL_STATE_DRAGGING || componentInfo.currentScrollState == SCROLL_STATE_IDLE) {
                        componentInfo.scroller.forceFinished(true);
                        componentInfo.totalScrollY = componentInfo.scroller.getCurrY();

                        float y = event.getY();

                        double l = Math.acos((radius - y) / radius) * radius;

                        int circlePosition = (int) ((l + halfItemHeight) / itemHeight);

                        // extraOffset 基本为0，可不处理
                        int extraOffset = calculateDestItemOffset(componentInfo.totalScrollY % itemHeight);
                        int destItemOffset = calculateItemScrollY(circlePosition, itemsVisibleCount / 2, itemHeight) + extraOffset;

                        destItemOffset = calculateScroll(componentInfo, destItemOffset);

                        if ((System.currentTimeMillis() - lastDownTime) > 120) {
                            smoothScroll(componentInfo, Action.DRAG, 0);
                        } else if (destItemOffset != 0) {
                            smoothScroll(componentInfo, Action.CLICK, destItemOffset);
                        }
                    }
                }

                break;
        }

        return true;
    }

    private int getComponentInRect(float x, float y) {

        for (int i = 0; i < componentInfos.size(); i++) {

            ComponentInfo componentInfo = componentInfos.get(i);

            int end;
            if (componentInfo.width == 0) {
                end = componentInfo.startX + componentWidth;
            } else {
                end = componentInfo.startX + componentInfo.width;
            }

            if (x > componentInfo.startX && x <= end) {
                return i;
            }
        }

        return INVALID_POSITION;
    }

    private int calculateDestItemOffset(int offset) {
        int extraOffset = offset % itemHeight;

        int destItemOffset;

        if (Math.abs(extraOffset) > (halfItemHeight)) {
            destItemOffset = (extraOffset > 0) ? (itemHeight - extraOffset) : -(extraOffset + itemHeight);
        } else {
            destItemOffset = -extraOffset;
        }

        return destItemOffset;
    }

    public boolean canScroll(int component, int offset) {
        ComponentInfo componentInfo = getComponentInfo(component);

        return (componentInfo != null) && (componentInfo.totalScrollY + offset >= componentInfo.minScrollY)
                && (componentInfo.totalScrollY + offset <= componentInfo.maxScrollY);
    }

    public int calculateScroll(int component, int offset) {
        ComponentInfo componentInfo = getComponentInfo(component);

        if (componentInfo == null) {
            return 0;
        }

        return calculateScroll(componentInfo, offset);
    }

    public int calculateScroll(ComponentInfo componentInfo, int offset) {
        if (componentInfo.totalScrollY + offset < componentInfo.minScrollY) {
            return componentInfo.minScrollY - componentInfo.totalScrollY;
        } else if (componentInfo.totalScrollY + offset > componentInfo.maxScrollY) {
            return componentInfo.maxScrollY - componentInfo.totalScrollY;
        } else {
            return offset;
        }
    }

    public boolean isShowSelectionIndicator() {
        return showSelectionIndicator;
    }

    public void setShowSelectionIndicator(boolean showSelectionIndicator) {
        this.showSelectionIndicator = showSelectionIndicator;
        invalidate();
    }

    class IndexString {

        int index;
        String string;

        public IndexString() {
            this.string = "";
        }

        public IndexString(int index, String str) {
            this.index = index;
            this.string = str;
        }

    }

    private final class PickerViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener {

        PickerViewGestureListener() {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return super.onSingleTapUp(e);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            ComponentInfo componentInfo = getComponentInfo(componentDragged);

            if (componentInfo == null) {
                return true;
            }

            int dy;
            int dstY = (int) (componentInfo.scroller.getCurrY() + distanceY);
            if (dstY < componentInfo.minScrollY) {
                dy = componentInfo.minScrollY - componentInfo.scroller.getCurrY();
            } else if (dstY > componentInfo.maxScrollY) {
                dy = componentInfo.maxScrollY - componentInfo.scroller.getCurrY();
            } else {
                dy = (int) distanceY;
            }

            if (DEBUG) {
                Log.d(TAG, "onScroll, x: " + componentInfo.scroller.getCurrX() + ", y:" + componentInfo.scroller.getCurrY());
                Log.d(TAG, "onScroll, finalX: " + componentInfo.scroller.getFinalX() + ", finalY:" + componentInfo.scroller.getFinalY());
                Log.d(TAG, "onScroll, distanceX: " + distanceX + ", distanceY:" + distanceY);
                Log.d(TAG, "onScroll, dY: " + dy);
            }

            componentInfo.scroller.startScroll(componentInfo.scroller.getCurrX(), componentInfo.scroller.getCurrY(), ((int) distanceX), dy, 0);
            onChangeScrollState(componentInfo, SCROLL_STATE_DRAGGING);
            ViewCompat.postInvalidateOnAnimation(PickerView.this);
            return true;
        }

        @Override
        public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            ComponentInfo componentInfo = getComponentInfo(componentDragged);

            if (componentInfo == null) {
                return true;
            }

            if (DEBUG) {
                Log.d(TAG, "onFling, x: " + componentInfo.scroller.getCurrX() +
                        ", y:" + componentInfo.scroller.getCurrY() +
                        ", velocityX: " + velocityX +
                        ", velocityY:" + velocityY);
            }

            componentInfo.scroller.forceFinished(true);
            componentInfo.scroller.fling(componentInfo.scroller.getStartX(),
                    componentInfo.scroller.getStartY(),
                    -((int) velocityX),
                    -((int) velocityY),
                    Integer.MIN_VALUE,
                    Integer.MAX_VALUE,
                    componentInfo.minScrollY,
                    componentInfo.maxScrollY);
            onChangeScrollState(componentInfo, SCROLL_STATE_FLING);
            ViewCompat.postInvalidateOnAnimation(PickerView.this);
            return true;
        }

    }

    public static class ComponentInfo {
        int index;
        Scroller scroller;
        int currentScrollState;
        int totalScrollY;
        int startX;
        int width;
        int initPosition;
        int lastItem;
        int currentItem;
        int scrollItem;
        int minScrollY;
        int maxScrollY;
        SparseArray<String> drawingStrings;

        public ComponentInfo(int index) {
            this.index = index;
            this.drawingStrings = new SparseArray<>();
        }
    }

    public interface OnItemChangeListener {

        void onItemSelected(int index, int component);

        /**
         * 滚轮滚动状态变化监听
         *
         * @param pickerView      PickerView
         * @param currentPassItem 当前经过的item
         * @param oldScrollState  上一次滚动状态
         * @param scrollState     当前滚动状态
         * @param totalScrollY    滚动距离
         */
        void onItemScrollStateChanged(PickerView pickerView, int currentPassItem, int oldScrollState, int scrollState, int totalScrollY);

        /***
         * 滚轮滚动监听
         * @param pickerView PickerView
         * @param currentPassItem 当前经过的item
         * @param scrollState 当前滚动状态
         */
        void onItemScrolling(PickerView pickerView, int currentPassItem, int component, int scrollState);

    }
}
